import threading
import datetime
import json

from jsonrpc import JSONRPCResponseManager, dispatcher
from voluptuous import Schema, MultipleInvalid, Required, Optional
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
from models import Job


class Server(threading.Thread):
    def __init__(self, logger, ip, port, jobHandler, jobList=[]):
        threading.Thread.__init__(self)
        self.daemon = True
        self._isOnline = True
        self._jobList = jobList
        self._jenkinsAddress = None
        self._jobHandler = jobHandler
        self._port = port
        self._ip = ip
        self._validationError = ''
        self._logger = logger
        self.prepareRequestsSchemas()
        self._headerLogInfo = "[" + "SERVER".rjust(19) + "]\t"
        self.start()

    def getResponse(self, ciAgentStatus, jobId, message='', status=0):
        self._logger.debug(self._headerLogInfo + "{} Preparing response with params: ".format(jobId) +
                           "ciAgentSatus = {}, message = {}, status = {}".format(ciAgentStatus, message, status))
        return {
            'ciAgentStatus': ciAgentStatus,
            'message': message,
            'position': self.queueNumber,
            'status': status
        }

    def prepareRequestsSchemas(self):
        self._requestSchema = Schema({
            Required('jsonrpc'): unicode,
            Required('method'): unicode,
            Required('params'): dict,
            Required('id'): int
        }, extra=True)
        self._paramsSchema = Schema({
            Required('project'): unicode,
            Optional('subproject'): unicode,
            Required('branch'): unicode,
            Required('testType'): unicode,
            Required('artifacts'): dict,
            Required('buildId'): unicode,
            Required('resultJob'): unicode,
            Required('timeout'): int,
            Required('force'): bool
        }, extra=True)

    def test(self, **kwargs):
        job = self.createJob(kwargs)
        self._logger.info(self._headerLogInfo + "{} Received Job request, params: {}"
                          .format(job._id, json.dumps(kwargs)))
        self.queueNumber = self._jobList.put(job)
        if self.queueNumber >= 0:
            self._logger.info(self._headerLogInfo + "{} Job queued".format(job._id))
            if self._isOnline:
                return self.getResponse('online', job._id, 'Job queued', 0)
            else:
                return self.getResponse('offline', job._id, 'Job queued', 0)
        elif self.queueNumber == -1:
            self._logger.warning(self._headerLogInfo + "{} Job could not be queued!".format(job._id))
            if self._isOnline:
                return self.getResponse('online', job._id, "Job could not be queued. Couldn't add job to queue!", 1)
            else:
                return self.getResponse('offline', job._id, "Job could not be queued. Couldn't add job to queue!", 1)

    def drop(self, **kwargs):
        job = self.getJob(kwargs)
        self._logger.info(self._headerLogInfo + "{} Job removal requested, params: {}"
                          .format(job._id, json.dumps(kwargs)))
        retcode = self._jobHandler.removeJob(job)
        if retcode == -1:
            if self._isOnline:
                return self.getResponse('online', job._id, "Job not found on execution list", -1)
            else:
                return self.getResponse('offline', job._id, "Job not found on execution list", -1)
        elif retcode == 0:
            if self._isOnline:
                return self.getResponse('online', job._id, "Job removed", 0)
            else:
                return self.getResponse('offline', job._id, "Job removed", 0)

    def getJob(self, params):
        return Job(
            project=params['project'],
            subproject='',
            branch=params['branch'],
            testType=params['testType'],
            artifacts='',
            buildId=params['buildId'],
            resultJob='',
            startTime='',
            timeout='',
            force='')

    def createJob(self, params):
        subproject = params['subproject'] if 'subproject' in params else 'None'
        return Job(
            project=params['project'],
            subproject=subproject,
            branch=params['branch'],
            testType=params['testType'],
            artifacts=params['artifacts'],
            buildId=params['buildId'],
            resultJob=params['resultJob'],
            startTime=datetime.datetime.now(),
            timeout=params['timeout'],
            force=params['force'])

    def polling(self, **kwargs):
        if self._isOnline:
            return self.getResponse('online', 'polling', 0)
        else:
            return self.getResponse('offline', 'polling', 0)

    def validateRequest(self, requestData):
        try:
            self._requestSchema(requestData)
        except MultipleInvalid as e:
            self._validationError = str(e)
        else:
            try:
                self._paramsSchema(requestData['params'])
            except MultipleInvalid as e:
                self._validationError = str(e)
            else:
                return True
        return False

    def validationFailed(self, **kwargs):
        return self._validationError

    @Request.application
    def application(self, request):
        self._jobList.setFrozen(True)
        self._logger.debug(self._headerLogInfo + "Received request")
        dispatcher["poll"] = self.polling
        dispatcher["test"] = self.test
        dispatcher["drop"] = self.drop
        dispatcher["validation_failed"] = self.validationFailed
        try:
            requestData = json.loads(request.data)
            if not self.validateRequest(requestData):
                requestData['method'] = "validation_failed"
            finalData = json.dumps(requestData)
            response = JSONRPCResponseManager.handle(finalData, dispatcher)
            resp = Response(response.json, mimetype='application/json')
            self._jobList.setFrozen(False)
            return resp
        except Exception as e:
            self._logger.exception(self._headerLogInfo + "BAD REQUEST! Skipped!: " + str(e))
            self._jobList.release()
            return Response('Server crash, request skipped: {}'.format(str(e)))

    def run(self):
        try:
            self._logger.info(self._headerLogInfo + "Server started")
            run_simple(self._ip, self._port, self.application, threaded=True)
        except Exception, e:
            self._logger.exception(self._headerLogInfo + "Server Crash: " + str(e))

    def goOffline(self):
        self._isOnline = False

    def goOnline(self):
        self._isOnline = True
